题意
洛谷:link
贝茜是一只非常缺觉的奶牛.她的一天被平均分割成 N 段(3≤N≤3830),但是她要用其中的 B 段时间(2≤B<N)睡觉。每段时间都有一个效用值 Ui(0≤Ui≤2×105),只有当她睡觉的时候,才会发挥效用。
有了闹钟的帮助,贝茜可以选择任意的时间入睡,当然,她只能在时间划分的边界处入睡、醒来。
贝茜想使所有睡觉效用的总和最大。不幸的是,每一段睡眠的第一个时间阶段都是“入睡”阶段,而旦不记入效用值。
时间阶段是不断循环的圆(一天一天是循环的嘛),假如贝茜在时间 N 和时间 1 睡觉,那么她将得到时间 1 的效用值。
思路
首先考虑如果时间不循环的做法
显然可以设计 dp,令 dpi,j,0/1 表示当前选到第 i 天,已经睡了 j 天,当前天睡了 / 没睡情况下的最大值
可以有如下转移
dpi,j,0dpi,j,1=min{dpi−1,j,0, dpi−1,j,1}=min{dpi−1,j,0, dpi−1,j,1+vi}
初始状态为 dp1,0,0=dp1,1,1=0, 其余均为 −∞
考虑如何断环,第一个想法便是将原数组复制一遍到原数组后边,这样即可求出不再同一天睡觉的情况
但这样真的正确吗?
这样做可能会导致一个问题:在原数组前边和后边选了同一段
例如在 [1,n] 区间中选了时段 x,在 [n+1,2n] 区间中又选了一次时段 x,这样会导致同一时间被选了多次,正确性有问题
那么如何解决呢?
考虑分类讨论,第 n 天如果睡觉的话,那么就可以得到第一天睡觉的收益,否则就无法得到
我们可以钦定第 n 天睡觉,同时将初始状态修改为 dp1,0,0=dp1,1,1=v1,求出答案,然后再不钦定第 n 天睡觉,求一遍答案,两次求得的答案取 max 即可
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| #include <bits/stdc++.h> namespace solution { typedef long long LL; const int MAXN = 4000 + 5; int n, m; LL ans = 0; LL v[MAXN]; LL dp[MAXN][2]; void solve() { for (int i = 2; i <= n; i++) { for (int j = m; j >= 1; j--) { dp[j][0] = std::max(dp[j][0], dp[j][1]); dp[j][1] = std::max(dp[j - 1][0], dp[j - 1][1] + v[i]); } } } int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++) { scanf("%lld", &v[i]); }
std::memset(dp, 0xcf, sizeof(dp)); dp[0][0] = 0, dp[1][1] = 0; solve(); ans = std::max({ans, dp[m][0], dp[m][1]});
std::memset(dp, 0xcf, sizeof(dp)); dp[0][0] = 0, dp[1][1] = v[1]; solve(); ans = std::max({ans, dp[m][1]});
printf("%lld\n", ans); return 0; } } int main() { solution::main(); return 0; }
|